/*
 * Network.cpp
 *
 *  Created on: May 28, 2011
 *      Author: Mario Hock
 */

#include "Network.h"

#include "messages/ExtendedChatMessage.h"
#include "Config.h"
#include "ariba/utility/system/EnterMethod.h"
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/local/stream_protocol.hpp>
#include <boost/foreach.hpp>
#include <boost/property_tree/exceptions.hpp>

using namespace std;

using ariba::utility::EnterMethod;
using boost::asio::ip::tcp;
using boost::asio::local::stream_protocol;

#define foreach BOOST_FOREACH


// TODO: class Omnibus ??

Network::Network() :
    finished(false)
{
}

Network::~Network()
{
}


void Network::load()
{
    if ( ! boost::filesystem::exists("config.xml") )
    {
        cout << "ERROR: config.xml not found" << endl << "quit." << endl;
        return;
    }

    // load config
    Config::instance().read("config.xml");

    string socket_path = "unix.sock";
    // create local unix socket / client connection
    if ( boost::filesystem::exists(socket_path) )
    {
        boost::filesystem::remove(socket_path);
    }
    stream_protocol::endpoint endpoint(socket_path);
    UI_multiplexer::instance().listen(endpoint);
    
    connect();
    
    // load userdatabase, transfers
    load_data();
    
    // user input
    main_loop();
}

// FIXME should be called before the ariba thread has started. is this possible?
void Network::load_data()
{
    /* Userdatabase */
    if ( boost::filesystem::exists("users.xml") )
    {
        cout << "LOADING USERDATABASE: " << endl;

        boost::property_tree::ptree pt;
        boost::property_tree::read_xml("users.xml", pt);
        users.load(pt.get_child("userdatabase"));

    }
    else
    {
        UI_multiplexer::instance().write_to_user(
                "no userdatabase, meet people with MEET");
    }


    /* Filetransfers */
    if ( boost::filesystem::exists("transfers.xml") )
    {
        cout << "LOADING TRANSFERS: " << endl;
//        ui_socket.write_to_user("LOADING TRANSFERS: ");

        boost::property_tree::ptree pt;
        boost::property_tree::read_xml("transfers.xml", pt);
        filetransfer->load(pt);
    }
    else
    {
        UI_multiplexer::instance().write_to_user("no transferdatabase");
    }

    
    /* transport, messages */
    path message_dir("messages");
    trans->load_arq_queue(message_dir);
}

// FIXME should be called after the ariba thread has ended. is this possible?
void Network::save_data()
{
    // FIXME kein Gültiges XML. Top Tag fehlt!

    // pretty xml output settings
    boost::property_tree::xml_writer_settings<char> w(' ', 4);
    
    /* users */
    boost::property_tree::ptree pt_users;
    pt_users.add_child("userdatabase", *users.save());
    boost::property_tree::write_xml("users.xml", pt_users, std::locale(), w);

    /* transfers */
    boost::property_tree::ptree pt_transfers;
    filetransfer->save(pt_transfers);
    boost::property_tree::write_xml("transfers.xml", pt_transfers, std::locale(), w);
    
    /* transport */
    path message_dir("messages");
    trans->save_arq_queue(message_dir);

    
    
    /* verbose output */
    UI_multiplexer::instance().write_to_user("SAVED");
    cout << "SAVED" << endl;
}



void Network::connect()
{
    /* conversion from omnibus-config format to the new ariba format */
    ptree ariba_config;
    ariba_config.put("spovnet_name", Config::instance().get("omnibus.spovnet_name"));
    ariba_config.put("node_name", Config::instance().get("omnibus.user_name"));
    ariba_config.put("listen_on.endp.category", "TCPIP");
    ariba_config.put("listen_on.endp.addr", Config::instance().get("omnibus.listen_on.ip_addr"));
    ariba_config.put("listen_on.endp.port", Config::instance().get("omnibus.listen_on.tcp_port"));
    ariba_config.put_child("bootstrap.direct", Config::instance().get_ariba_bootstrap_hints());


    UserPtr I = User::createUser(ariba_config.get<string>("node_name"));
    users.set_myself(I);

	// Create Transport
    trans = new Transport(node, users);

	node.bind(trans);
	node.start();

	// output
	cout << "Initiating and joining the SpoVNet '" + ariba_config.get<string>("spovnet_name") + "'..." << endl;

	// Initiate and join the Spovnet
	node.connect(ariba_config);

	// create Services
    phrase_connector = new Phrase_connector(users, node, *trans);
    filetransfer = new Filetransfer(&users, trans);
    dir_syncer = new Dir_syncer(&users, trans, filetransfer);
    chat_service = new Chat(&users, trans);
}

void Network::disconnect()
{
    // leave spovnet
    node.leave();
    
    // unbind node listener
    node.unbind( trans );

    if (phrase_connector)
    {
        delete phrase_connector;
        phrase_connector = NULL;
    }
    
    if (filetransfer)
    {
        delete filetransfer;
        filetransfer = NULL;
    }
    
    if (trans)
    {
        delete trans;
        trans = NULL;
    }
}


int Network::main_loop()
{
    string message;
    bool done = false;

    UI_multiplexer::instance().write_to_user(
            "Node active: " + node.getNodeId().getAddress());

    // connect to filetransfer partners
    ptree* comm_pt = new ptree();
    comm_pt->put("conp.tries", 0);
    SystemEventType TestEvent("This is a test event!");
    SystemQueue::instance().scheduleEvent( SystemEvent( this, TestEvent, comm_pt), 1000 );
    comm_pt = NULL;
    // ---------------------------------------
    
    UI_multiplexer::instance().run();
//    // actual loop
//    while ( ! done )
//    {
//        // read from socket
//        message = ui_socket.read_from_user();
//        if ( message == "quit" )
//            done = true;
//
//        // enqueue in ariba system queue
//        SystemEventType CommandEvent("Command from UI");
//        string* s = new string(message);
//        SystemQueue::instance().scheduleEvent( SystemEvent(this, CommandEvent, s) );
//    }


    /* quit */
    while ( !finished )
    {
        cout << "waiting..." << endl;
        usleep(200);
        
        // TODO join the ariba thread instead?
        // TODO or SystemQueue::cancel() ?
    }

    cout << "bye." << endl;

    return 0;
}


/* Event Handler, in sync with Ariba */
void Network::handleSystemEvent(const SystemEvent& event)
{
    // get data pointer
    ptree* command_pt = event.getData<ptree>();
    
    // hand over to command handler
    execute_ui_commmands(*command_pt);
    
    // free data pointer
    delete command_pt;
}

/* this function delegates XML commands to the appropriate modules */
void Network::execute_ui_commmands(ptree& command_pt)
{
    string root_tag = command_pt.front().first;

//    cout << "ROOT TAG: " << root_tag << endl;

    if ( root_tag == "legacy" )
    {
        cout << "GOT LEGACY COMMAND: " << command_pt.front().second.data() << endl;
        parseCommands(command_pt.front().second.data());
    }

    // BRNACH: chat
    else if ( root_tag == "chat" )
    {
        if ( chat_service )  // TODO singleton...
        {
            chat_service->send_chat(command_pt, UI_multiplexer::instance());  // TODO sollte Antwort-UI übergeben, nicht die Allgemeine
                                                                              // XXX wieso...? chat-echo soll doch an alle gehen...
        }
    }
    
    // connect filetransfer partners
    else if ( root_tag == "conp" )
    {
        // BRANCH: try to connect if there is an overlay connection
        if ( node.getNeighborNodes().size() > 0 )
        {
            UI_multiplexer::instance().write_to_user(trans->overlay_info());
            filetransfer->connect_partners();
        }
        // BRANCH: wait for it...
        else
        {
            int tries = command_pt.get<int>("conp.tries");
            uint32_t wait = 5000;
            
            // the first three time, do quick retry. then slowly..
            if ( tries < 3 )
            {
                tries++;
                wait = 1000;
            }
            
            ptree* comm_pt = new ptree();
            comm_pt->put("conp.tries", tries);
            SystemEventType TestEvent("This is a test event!");
            SystemQueue::instance().scheduleEvent( SystemEvent( this, TestEvent, comm_pt), wait );
            comm_pt = NULL;
        }
    }
    
    
    // BRANCH: pushsync   XXX experimentell!!
    else if ( root_tag == "pushsync" )
    {
        try
        {
            string name = command_pt.get<string>("pushsync.name");
            string path = command_pt.get<string>("pushsync.path");
            
            UserPtr partner = users.getUser(name);
            
            if (partner)
            {
                dir_syncer->initiate_push_sync(partner, path);
                //dir_syncer->initiate_pull_sync(partner, path);
            }
            else
            {
              UI_multiplexer::instance().write_to_user(
                      "NO USER called '" + name + "' in database.");
            }
        }
        catch (boost::property_tree::ptree_error& e)
        {
            ostringstream out;
            out << "syntax error: " << e.what();
            UI_multiplexer::instance().write_warn(out.str());
        }
    }
    // BRANCH: pullsync   XXX experimentell!!
    else if ( root_tag == "pullsync" )
    {
        try
        {
            string name = command_pt.get<string>("pullsync.name");
            string path = command_pt.get<string>("pullsync.path");
            
            UserPtr partner = users.getUser(name);
            
            if (partner)
            {
                dir_syncer->initiate_pull_sync(partner, path);
            }
            else
            {
              UI_multiplexer::instance().write_to_user(
                      "NO USER called '" + name + "' in database.");
            }
        }
        catch (boost::property_tree::ptree_error& e)
        {
            ostringstream out;
            out << "syntax error: " << e.what();
            UI_multiplexer::instance().write_warn(out.str());
        }
    }

}

/* this function parses and executes (non XML) commands from the UIs */
void Network::parseCommands(string& message)
{
    // befehl extrahieren
    string o = message.substr(0, 4);
    string s;
    if ( message.length() > 5 )
      s = message.substr(5, message.length()-4);


    /* Befehle */
    // quit
    if ( o == "quit" )
    {
        filetransfer->shut_down();
        save_data();

        // close all open connections
        trans->close_all_connections();

        // close the ui connections
        UI_multiplexer::instance().stop();

        // signal main loop we have finished
        finished = true;
    }

    // initiate a new sync
    else if ( o == "SYNC" )
    {
      string name;
      string path;

      size_t pos;
      pos = s.find(" ");
      if ( pos > 0 )
      {
        name = s.substr(0, pos);
        path = s.substr(pos+1, s.length()-pos);

        UserPtr partner = users.getUser(name);

        if (partner)
        {
//            dir_syncer->initiate_push_sync(partner, path);
            dir_syncer->initiate_pull_sync(partner, path);
        }
        else
        {
            UI_multiplexer::instance().write_to_user(
                    "NO USER called '" + name + "' in database.");
        }
      }
      else
      {
          UI_multiplexer::instance().write_to_user("syntax error");
      }
    }

    // push a file to another user
    else if ( o == "PUSH" )
    {
      string name;
      string file;

      size_t pos;
      pos = s.find(" ");
      if ( pos > 0 )
      {
        name = s.substr(0, pos);
        file = s.substr(pos+1, s.length()-pos);

        UserPtr dest = users.getUser(name);

        if (dest)
        {
          filetransfer->push_file(dest, file);
        }
        else
        {
          UI_multiplexer::instance().write_to_user(
                  "NO USER called '" + name + "' in database.");
        }
      }
      else
      {
          UI_multiplexer::instance().write_to_user("syntax error");
      }
    }

    // accept push request
    else if ( o == "ACPT")
    {
      filetransfer->accept_file(s);
    }
    // decline push request
    else if ( o == "DECL")
    {
      filetransfer->decline_file(s);
    }
    // abort outgoing transmission
    else if ( o == "ABRT")
    {
      filetransfer->abort_transfer(s);
    }
    // pause outgoing transmission
    else if ( o == "STOP")
    {
      filetransfer->pause_transfer(s, false);
    }
    // pause outgoing transmission
    else if ( o == "WAIT")
    {
      filetransfer->demand_pause(s);
    }
    // continue outgoing transmission
    else if ( o == "CONT")
    {
      filetransfer->resume_transfer(s);
    }
    // continue outgoing transmission
    else if ( o == "GOON")
    {
      filetransfer->demand_resume(s);
    }
    // open a received file
    else if ( o == "OPEN" )
    {
        filetransfer->open_file(s);
    }
    // open incoming dir
    else if ( o == "OINC" )
    {
        filetransfer->open_incoming_dir();
    }


    // custody, upload file to a server
    else if ( o == "CSDY" )
    {
        string name;
        string file;
        string server = "Server"; // TODO auswählbar (XML)

        size_t pos;
        pos = s.find(" ");
        if ( pos > 0 )
        {
            server = s.substr(0, pos);
            // XXX dieser Teil ist nicht schön..
            string tmp = s.substr(pos+1, s.length()-pos);
            pos = tmp.find(" ");
            if ( pos > 0 )
            {
                name = tmp.substr(0, pos);
                file = tmp.substr(pos+1, tmp.length()-pos);
            }
            else
            {
                UI_multiplexer::instance().write_to_user(
                        "ERROR: CSDY syntax error");
            }

            UserPtr receiver = users.getUser(name);
            UserPtr srv = users.getUser(server);

            if (srv && receiver)
            {
                filetransfer->upload_to_server(srv, receiver, file);
            }
            else
            {
                UI_multiplexer::instance().write_to_user(
                        "NO USER called '" + name + "' or '" + 
                        server + "' in database.");
            }
        }
        else
        {
            UI_multiplexer::instance().write_to_user(
                    "ERROR: CSDY syntax error");
        }
    }


    else if ( o == "MEET")
    {
        phrase_connector->meet(s);
    }
    else if ( o == "LIST")
    {
//        ui_socket.write_to_user("USERS:\n" + users.list());
        UI_multiplexer::instance().write_to_user(users.list());

        // XXX auch mal hier rein...
        UI_multiplexer::instance().write_to_user(trans->overlay_info());
    }

    // save state
    else if ( o == "SAVE")
    {
        save_data();
    }

    // status of filetransfers
    else if ( o == "STAT" )
    {
        filetransfer->status();
    }

    // detailed infos about user  --  TODO aufruf inkompatibel geworden. aber brauchen wir dieses commando überhaupt?
    else if ( o == "SHOW" )
    {
//        string name;
//        string dir;
//
//        int pos = s.find(" ");
//        // BRANCH: subdir
//        if ( pos > 0 )
//        {
//            name = s.substr(0, pos);
//            dir = s.substr(pos+1, s.length()-pos);
//        }
//        // BRANCH: no-subdirs
//        else
//        {
//            name = s;
//            dir = "";
//        }
//
//

        string name = s;
        UserPtr u = users.getUser(name);

        if (u)
        {
            ptree pt;
            u->list_shares(pt);

            ostringstream out;
            boost::property_tree::write_xml(out, pt);

            UI_multiplexer::instance().write_to_user(out.str());
        }
        else
        {
            UI_multiplexer::instance().write_to_user(
                    "NO USER called '" + name + "' in database.");
        }
    }

    // show accessible files at another user
    else if ( o == "LOOK" )
    {
        string name;
        string dir;

        int pos = s.find(" ");
        // BRANCH: subdir
        if ( pos > 0 )
        {
            name = s.substr(0, pos);
            dir = s.substr(pos+1, s.length()-pos);
        }
        // BRANCH: no-subdirs
        else
        {
            name = s;
            dir = "";
        }


        UserPtr u = users.getUser(name);

        if (u)
        {
            filetransfer->look(u, dir);
        }
        else
        {
            UI_multiplexer::instance().write_to_user(
                    "NO USER called '" + name + "' in database.");
        }
    }

    // pull file from another user
    else if ( o == "PULL" )
    {
        string name;
        string dir;

        int pos = s.find(" ");
        // OK: path
        if ( pos > 0 )
        {
            name = s.substr(0, pos);
            dir = s.substr(pos+1, s.length()-pos);


            UserPtr u = users.getUser(name);

            if (u)
            {
                filetransfer->pull_file(u, dir);
            }
            else
            {
                UI_multiplexer::instance().write_to_user(
                        "NO USER called '" + name + "' in database.");
            }
        }
        // ERR: syntax error
        else
        {
            UI_multiplexer::instance().write_to_user(
                    "Tell me, which file do you want?");
        }
    }

    // connect
    else if ( o == "CONN" )
    {
        UserPtr u = users.getUser(s);

        if ( u )
        {
            trans->establish_connection(u);
        }
        else
        {
            UI_multiplexer::instance().write_to_user(
                    "NO USER called '" + s + "' in database.");
        }
    }

    // disconnect
    else if ( o == "DISC" )
    {
        UserPtr u = users.getUser(s);

        if ( u )
        {
            trans->close_connection(u);
        }
        else
        {
            UI_multiplexer::instance().write_to_user(
                    "NO USER called '" + s + "' in database.");
        }
    }

    // pause all transfers
    else if ( o == "PALL" )
        filetransfer->pause_all_transfers(false);

    // queries the overlay status
    else if ( o == "ONLN" )
    {
        UI_multiplexer::instance().write_to_user(trans->overlay_info());
    }

//    else if ( o == "BUGS" )
//    {
//        UserPtr u = users.getUser(s);
//
//        if ( u )
//        {
//            filetransfer->bug_reproduce(u);
//        }
//        else
//        {
//            UI_multiplexer::instance().write_to_user(
//                    "NO USER called '" + s + "' in database.");
//        }
//    }


    // mark downloadable (share)
    else if ( o == "GIVE" )
    {
        string name;
        string dir;

        int pos = s.find(" ");
        // OK: path
        if ( pos > 0 )
        {
            name = s.substr(0, pos);
            dir = s.substr(pos+1, s.length()-pos);


            UserPtr u = users.getUser(name);

            if (u)
            {
                u->grant_file_permissions(dir, Access_attributes::PULL);
            }
            else
            {
                UI_multiplexer::instance().write_to_user(
                        "NO USER called '" + name + "' in database.");
            }
        }
        // ERR: syntax error
        else
        {
            UI_multiplexer::instance().write_to_user(
                    "Tell me, which file do you want to share?");
        }
    }


    // XXX
    else if ( o == "BLCK" )
    {
    }

    // command not found
    else
    {
        UI_multiplexer::instance().write_to_user(
                "COMMAND '" + o + "' NOT FOUND");
    }

}
